package org.infodavid.common.impl.services;

import java.util.Collection;
import java.util.Date;
import java.util.Iterator;

import javax.annotation.Resource;
import javax.ejb.NoSuchObjectLocalException;
import javax.ejb.SessionContext;
import javax.ejb.Timeout;
import javax.ejb.Timer;
import javax.ejb.TimerService;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.infodavid.common.impl.tasks.AbstractTasksProcessor;

/**
 * The Class AbstractProcessingBean.
 */
@TransactionManagement(TransactionManagementType.CONTAINER)
public abstract class AbstractProcessingBean extends AbstractTasksProcessor {
  /** The Constant log. */
  private static final Log LOGGER = LogFactory.getLog(AbstractProcessingBean.class);

  /** The context. */
  private @Resource
  SessionContext context;

  /**
   * Gets the context.
   * @return the context
   */
  public SessionContext getContext() {
    return context;
  }

  /**
   * Register the timer.
   */
  @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
  public void registerTimer() {
    final boolean isDebugEnabled = LOGGER.isDebugEnabled();

    if (isDebugEnabled) {
      LOGGER.debug("Context: " + context);
    }

    removeAllTimers();
    createTimer();
  }

  /**
   * Unregister the timer.
   */
  @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
  public void unregisterTimer() {
    final boolean isDebugEnabled = LOGGER.isDebugEnabled();

    if (isDebugEnabled) {
      LOGGER.debug("Context: " + context);
    }

    removeAllTimers();
  }

  /**
   * Creates the timer.
   */
  protected void createTimer() {
    final boolean isDebugEnabled = LOGGER.isDebugEnabled();
    final TimerService service = context.getTimerService();
    final Date startDate = getProcessingDate();
    final String name = getName();

    if (isDebugEnabled) {
      LOGGER.debug("Scheduling daily processing starting at " + startDate);
    }

    service.createTimer(startDate, name);

    LOGGER.info("Timer '" + name + "' scheduled at " + startDate);
  }

  /**
   * Removes the all timers.
   */
  @SuppressWarnings("rawtypes")
  protected void removeAllTimers() {
    final boolean isDebugEnabled = LOGGER.isDebugEnabled();
    final TimerService service = context.getTimerService();
    final Collection timers = service.getTimers();
    final String name = getName();

    if (!(timers == null || timers.isEmpty())) {
      final Iterator ite = timers.iterator();
      Timer timer = null;

      while (timer == null && ite.hasNext()) {
        timer = (Timer)ite.next();

        if (name.equals(timer.getInfo())) {
          if (isDebugEnabled) {
            LOGGER.debug("Cancelling the existing timer: " + timer);
          }

          timer.cancel();
        }
      }
    }
  }

  /**
   * Gets the name.
   * @return the name
   */
  protected abstract String getName();

  /**
   * Gets the processing date.
   * @return the processing date
   */
  protected abstract Date getProcessingDate();

  /**
   * Timeout handler.
   * @param timer the timer
   * @throws Exception the exception
   */
  @Timeout
  @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
  public synchronized void timeoutHandler(final Timer timer) throws Exception {
    try {
      process(timer);
    }
    finally {
      // we reschedule the task using the current hour and minute
      try {
        timer.cancel();
        createTimer();
      }
      catch (final NoSuchObjectLocalException e) {
        LOGGER.debug(
            "Timer was already cancelled or is associated to an old version of the application", e);
        LOGGER
            .info("Timer was already cancelled or is associated to an old version of the application ("
                + e.getMessage() + ')');
      }
    }
  }

  /**
   * Process.
   * @param timer the timer
   * @throws Exception the exception
   */
  protected abstract void process(Timer timer) throws Exception;
}
